// 版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

package ssa

import (
	"cmd/internal/src"
	"fmt"
	"sort"
)

func isPoorStatementOp(op Op) bool {
	switch op {
	// 注意Nilcheck经常消失，但当它不消失时，您希望从那里开始陈述
	// 以便调试器用户在死机之前看到停止，并可以检查该值。
	case OpAddr, OpLocalAddr, OpOffPtr, OpStructSelect, OpPhi, OpITab, OpIData,
		OpIMake, OpStringMake, OpSliceMake, OpStructMake0, OpStructMake1, OpStructMake2, OpStructMake3, OpStructMake4,
		OpConstBool, OpConst8, OpConst16, OpConst32, OpConst64, OpConst32F, OpConst64F, OpSB, OpSP,
		OpArgIntReg, OpArgFloatReg:
		return true
	}
	return false
}

// nextGoodStatementIndex返回一个在i或更高版本的索引
// 是一个很好的开始b声明的地方。这个决定是正确的
// 根据v的Op，更好的后期操作的可能性，以及
// i后面的值是否与v相同。
// 如果找不到更好的语句索引，则返回i。
func nextGoodStatementIndex(v *Value, i int, b *Block) int {
	// 如果该值是块中的最后一个值，太糟糕了，它将不得不这样做
	// （这假设值顺序模糊地对应于源
	// 程序执行顺序，在ssa执行后往往直接为真
	// 第一次建造。
	if i >= len(b.Values)-1 {
		return i
	}
	// 跳过可能在重写过程中消失的短暂/脆弱操作码。
	if !isPoorStatementOp(v.Op) {
		return i
	}
	// 向前看，看看下一个边界上的行号是什么。
	for j := i + 1; j < len(b.Values); j++ {
		u := b.Values[j]
		if u.Pos.IsStmt() == src.PosNotStmt { // 忽略非声明
			continue
		}
		if u.Pos.SameFileAndLine(v.Pos) {
			if isPoorStatementOp(u.Op) {
				continue // 继续看，这也不是一个好的声明
			}
			return j
		}
		return i
	}
	return i
}

// notStmtBoundary报告操作码为op的值是否永远不能成为语句
// 边界这样的值与用户对应用程序的理解不一致
// 语句边界。
func notStmtBoundary(op Op) bool {
	switch op {
	case OpCopy, OpPhi, OpVarKill, OpVarDef, OpVarLive, OpUnknown, OpFwdRef, OpArg, OpArgIntReg, OpArgFloatReg:
		return true
	}
	return false
}

func (b *Block) FirstPossibleStmtValue() *Value {
	for _, v := range b.Values {
		if notStmtBoundary(v.Op) {
			continue
		}
		return v
	}
	return nil
}

func flc(p src.XPos) string {
	if p == src.NoXPos {
		return "none"
	}
	return fmt.Sprintf("(%d):%d:%d", p.FileIndex(), p.Line(), p.Col())
}

type fileAndPair struct {
	f  int32
	lp lineRange
}

type fileAndPairs []fileAndPair

func (fap fileAndPairs) Len() int {
	return len(fap)
}
func (fap fileAndPairs) Less(i, j int) bool {
	return fap[i].f < fap[j].f
}
func (fap fileAndPairs) Swap(i, j int) {
	fap[i], fap[j] = fap[j], fap[i]
}

// -d=ssa/number_line/stats=1（该位）用于行和文件分布统计
// -d=ssa/number_lines/debug，了解特定值标记为语句的原因。
func numberLines(f *Func) {
	po := f.Postorder()
	endlines := make(map[ID]src.XPos)
	ranges := make(map[int]lineRange)
	note := func(p src.XPos) {
		line := uint32(p.Line())
		i := int(p.FileIndex())
		lp, found := ranges[i]
		change := false
		if line < lp.first || !found {
			lp.first = line
			change = true
		}
		if line > lp.last {
			lp.last = line
			change = true
		}
		if change {
			ranges[i] = lp
		}
	}

	// 按相反的post顺序访问，以便所有非循环前置项都排在第一位。
	for j := len(po) - 1; j >= 0; j-- {
		b := po[j]
		// 找到第一个有趣的位置并检查它是否与任何先前位置不同
		firstPos := src.NoXPos
		firstPosIndex := -1
		if b.Pos.IsStmt() != src.PosNotStmt {
			note(b.Pos)
		}
		for i := 0; i < len(b.Values); i++ {
			v := b.Values[i]
			if v.Pos.IsStmt() != src.PosNotStmt {
				note(v.Pos)
				// 如有可能，请跳到这一行的更好说明
				i = nextGoodStatementIndex(v, i, b)
				v = b.Values[i]
				firstPosIndex = i
				firstPos = v.Pos
				v.Pos = firstPos.WithDefaultStmt() // 违约
				break
			}
		}

		if firstPosIndex == -1 { // 有效的空块，检查块自身的POS，考虑PREDS。
			line := src.NoXPos
			for _, p := range b.Preds {
				pbi := p.Block().ID
				if !endlines[pbi].SameFileAndLine(line) {
					if line == src.NoXPos {
						line = endlines[pbi]
						continue
					} else {
						line = src.NoXPos
						break
					}

				}
			}
			// 如果块本身没有语句并且实际上是空的，则将其标记为带有前置语句，但不能标记为语句
			if b.Pos.IsStmt() == src.PosNotStmt {
				b.Pos = line
				endlines[b.ID] = line
				continue
			}
			// 如果该块与其前一个块不同，请将其标记为语句
			if line == src.NoXPos || !line.SameFileAndLine(b.Pos) {
				b.Pos = b.Pos.WithIsStmt()
				if f.pass.debug > 0 {
					fmt.Printf("Mark stmt effectively-empty-block %s %s %s\n", f.Name, b, flc(b.Pos))
				}
			}
			endlines[b.ID] = b.Pos
			continue
		}
		// 检查是否存在任何差异；如果firstPos不同，则它是一个边界。
		if len(b.Preds) == 0 { // 别忘了进入区
			b.Values[firstPosIndex].Pos = firstPos.WithIsStmt()
			if f.pass.debug > 0 {
				fmt.Printf("Mark stmt entry-block %s %s %s %s\n", f.Name, b, b.Values[firstPosIndex], flc(firstPos))
			}
		} else { // 不同的pred
			for _, p := range b.Preds {
				pbi := p.Block().ID
				if !endlines[pbi].SameFileAndLine(firstPos) {
					b.Values[firstPosIndex].Pos = firstPos.WithIsStmt()
					if f.pass.debug > 0 {
						fmt.Printf("Mark stmt differing-pred %s %s %s %s, different=%s ending %s\n",
							f.Name, b, b.Values[firstPosIndex], flc(firstPos), p.Block(), flc(endlines[pbi]))
					}
					break
				}
			}
		}
		// 将每个新的（感兴趣的）位置设置为语句边界，并向前迭代。
		for i := firstPosIndex + 1; i < len(b.Values); i++ {
			v := b.Values[i]
			if v.Pos.IsStmt() == src.PosNotStmt {
				continue
			}
			note(v.Pos)
			// 如果可能，请跳到前面
			i = nextGoodStatementIndex(v, i, b)
			v = b.Values[i]
			if !v.Pos.SameFileAndLine(firstPos) {
				if f.pass.debug > 0 {
					fmt.Printf("Mark stmt new line %s %s %s %s prev pos = %s\n", f.Name, b, v, flc(v.Pos), flc(firstPos))
				}
				firstPos = v.Pos
				v.Pos = v.Pos.WithIsStmt()
			} else {
				v.Pos = v.Pos.WithDefaultStmt()
			}
		}
		if b.Pos.IsStmt() != src.PosNotStmt && !b.Pos.SameFileAndLine(firstPos) {
			if f.pass.debug > 0 {
				fmt.Printf("Mark stmt end of block differs %s %s %s prev pos = %s\n", f.Name, b, flc(b.Pos), flc(firstPos))
			}
			b.Pos = b.Pos.WithIsStmt()
			firstPos = b.Pos
		}
		endlines[b.ID] = firstPos
	}
	if f.pass.stats&1 != 0 {
		// 报告将要构建的稀疏贴图形状的摘要统计信息
		// TODO使用此信息可以更快地生成稀疏贴图。
		var entries fileAndPairs
		for k, v := range ranges {
			entries = append(entries, fileAndPair{int32(k), v})
		}
		sort.Sort(entries)
		total := uint64(0)            // maxline（文件）-minline（文件）的文件总和
		maxfile := int32(0)           // 最大（文件索引）
		minline := uint32(0xffffffff) // minline（文件）的最小覆盖文件数
		maxline := uint32(0)          // maxline的最大覆盖文件数（文件）
		for _, v := range entries {
			if f.pass.stats > 1 {
				f.LogStat("file", v.f, "low", v.lp.first, "high", v.lp.last)
			}
			total += uint64(v.lp.last - v.lp.first)
			if maxfile < v.f {
				maxfile = v.f
			}
			if minline > v.lp.first {
				minline = v.lp.first
			}
			if maxline < v.lp.last {
				maxline = v.lp.last
			}
		}
		f.LogStat("SUM_LINE_RANGE", total, "MAXMIN_LINE_RANGE", maxline-minline, "MAXFILE", maxfile, "NFILES", len(entries))
	}
	// cachedLineStarts是包含在范围内的值的空稀疏映射。
	f.cachedLineStarts = newXposmap(ranges)
}
